/***************************************************************************** 
* 
* File Name : main.c
* 
* Description: main 
* 
* Author : WangWei
* 
* Date : 2022-04-01
*****************************************************************************/ 
#include <string.h>
#include "wm_include.h"
#include "wm_gpio_afsel.h"
#include "wm_uart.h"

#include "lua/lua.h"
#include "lua/lauxlib.h"
#include "lua/lualib.h"

#define LUA_PROGNAME				"lua"
#define UART_NUM					TLS_UART_0
#define LUA_TASK_PRIO	            32
#define LUA_TAST_STK_SIZE			5120
#define LUA_MAXINPUT				512
#define LUA_QUEUE_SIZE				32
#define TEMP_BUFF_SIZE				1024

#define LUA_PROMPT					"> "
#define LUA_PROMPT2					">> "

#define LUA_MSG_UART_RECEIVE_DATA	1

static OS_STK lua_task_stk[LUA_TAST_STK_SIZE];
static const char *progname = LUA_PROGNAME;
static u8 rx_buffer[TLS_UART_RX_BUF_SIZE] = {0};
static u8 temp_buffer[TEMP_BUFF_SIZE] = {0};
static tls_os_queue_t *lua_uart_q;
static bool isUartDelay = false;

/*
** Return the string to be used as a prompt by the interpreter. Leave
** the string (or nil, if using the default value) on the stack, to keep
** it anchored.
*/
static const char *get_prompt (lua_State *L, int firstline) {
	if (lua_getglobal(L, firstline ? "_PROMPT" : "_PROMPT2") == LUA_TNIL) {
		return (firstline ? LUA_PROMPT : LUA_PROMPT2);  /* use the default */
	} else {  /* apply 'tostring' over the value */
		const char *p = luaL_tolstring(L, -1, NULL);
		lua_remove(L, -2);  /* remove original value */
		return p;
	}
}

/*
** Prints an error message, adding the program name in front of it
** (if present)
*/
static void l_message (const char *pname, const char *msg) {
  if (pname) lua_writestringerror("%s: ", pname);
  lua_writestringerror("%s\n", msg);
}

/*
** Prints (calling the Lua 'print' function) any values on the stack
*/
static void l_print (lua_State *L) {
	int n = lua_gettop(L);
	if (n > 0) {  /* any result to be printed? */
		luaL_checkstack(L, LUA_MINSTACK, "too many results to print");
		lua_getglobal(L, "print");
		lua_insert(L, 1);
		if (lua_pcall(L, n, 0, 0) != LUA_OK) {
			l_message(progname, lua_pushfstring(L, "error calling 'print' (%s)", lua_tostring(L, -1)));
		}
	}
}

/*
** Check whether 'status' is not OK and, if so, prints the error
** message on the top of the stack. It assumes that the error object
** is a string, as it was either generated by Lua or by 'msghandler'.
*/
static int report (lua_State *L, int status) {
	if (status != LUA_OK) {
		const char *msg = lua_tostring(L, -1);
		l_message(progname, msg);
		lua_pop(L, 1);  // remove message 
	}
	return status;
}

static void print_version (void) 
{
	lua_writestring(LUA_COPYRIGHT, strlen(LUA_COPYRIGHT));
	lua_writeline();
	printf("\n");
}

static s16 uart_rx_callback(u16 len, void *p)
{
	//printf("ready for data\n");
	memset(temp_buffer, 0, TEMP_BUFF_SIZE);
	/*if (isUartDelay) {
		int ret = tls_uart_read(UART_NUM, (u8 *)temp_buffer, len);
		if (ret > 0) {
			//printf("clear %d\n", ret);
		}
		return WM_SUCCESS;
	}*/
	int ret = tls_uart_read(UART_NUM, (u8 *)temp_buffer, len);  /* input */
	if (ret > 0) {
		if (strstr(temp_buffer, "\n") != NULL || strstr(temp_buffer, "\r")) { // end line, trigger event
			strcat((char *)rx_buffer, (const char *)temp_buffer);
			tls_os_queue_send(lua_uart_q, (void *) LUA_MSG_UART_RECEIVE_DATA, 0);
		} else {
			if (temp_buffer[0] == 0x8) { // press Backspace button, delete one char
				int index = strlen((const char *)rx_buffer) - 1;
				if (index >= 0) {
					rx_buffer[index] = 0;
					//char back[1] = {0x10};
					tls_uart_write(UART_NUM, "\b", 1);
				} // else no more character can be removed
			} else {
				if (strlen((const char *)rx_buffer) + strlen(temp_buffer) >= TLS_UART_RX_BUF_SIZE) { //overflow
					printf("\n rx buffer overflow \n");
				} else {
					strcat((char *)rx_buffer, (const char *)temp_buffer); //append string
					tls_uart_write(UART_NUM, (char *)temp_buffer, ret);  /* output */
				}
			}
		}
	}
	return WM_SUCCESS;
}

static s16 uart_tx_sent_callback(struct tls_uart_port *port)
{
	//printf("uart tx callback\n");
	return WM_SUCCESS;
}

static void uart_init(void) 
{
#if 0
	tls_uart_options_t opt;
	opt.baudrate = UART_BAUDRATE_B115200;
    opt.paritytype = TLS_UART_PMODE_DISABLED;
    opt.stopbits = TLS_UART_ONE_STOPBITS;
    opt.charlength = TLS_UART_CHSIZE_8BIT;
    opt.flow_ctrl = TLS_UART_FLOW_CTRL_NONE;
	// UART0_RX-PB20  UART0_TX-PB19 
    wm_uart0_rx_config(WM_IO_PB_20);
    wm_uart0_tx_config(WM_IO_PB_19);
	if (WM_SUCCESS != tls_uart_port_init(UART_NUM, &opt, 0)) {
		printf("\nUart init failed\n");
		return;
	} else {
		printf("\nUart init success\n");
	}
#endif

	tls_uart_set_baud_rate(TLS_UART_0, 115200);
	printf("register rx/tx callback\n");
	
	tls_uart_rx_callback_register((u16) UART_NUM, uart_rx_callback, NULL);
	tls_uart_tx_callback_register((u16) UART_NUM, uart_tx_sent_callback);
}

static int lua_readline(lua_State *L, char *buff, const char *prmt)
{
	int len = strlen((const char *)rx_buffer);
	if (len == 0) {
		return 0;
	}
	memcpy(buff, rx_buffer, len);
	memset(rx_buffer, 0, TLS_UART_RX_BUF_SIZE);
	return len;
}

static void lua_initreadline(lua_State *L)
{
}

static void lua_saveline(lua_State *L, const char *line)
{
}

/*
** Message handler used to run all chunks
*/
static int msghandler (lua_State *L) {
  const char *msg = lua_tostring(L, 1);
  if (msg == NULL) {  /* is error object not a string? */
    if (luaL_callmeta(L, 1, "__tostring") &&  /* does it have a metamethod */
        lua_type(L, -1) == LUA_TSTRING)  /* that produces a string? */
      return 1;  /* that is the message */
    else
      msg = lua_pushfstring(L, "(error object is a %s value)",
                               luaL_typename(L, 1));
  }
  luaL_traceback(L, L, msg, 1);  /* append a standard traceback */
  return 1;  /* return the traceback */
}

/*
** Interface to 'lua_pcall', which sets appropriate message function
** and C-signal handler. Used to run all chunks.
*/
static int docall (lua_State *L, int narg, int nres) {
	int status;
	int base = lua_gettop(L) - narg;  /* function index */
	lua_pushcfunction(L, msghandler);  /* push message handler */
	lua_insert(L, base);  /* put it under function and args */
	//globalL = L;  /* to be available to 'laction' */
	//setsignal(SIGINT, laction);  /* set C-signal handler */
	status = lua_pcall(L, narg, nres, base);
	//setsignal(SIGINT, SIG_DFL); /* reset C-signal handler */
	lua_remove(L, base);  /* remove message handler from the stack */
	return status;
}

/*
** Prompt the user, read a line, and push it into the Lua stack.
*/
static int pushline (lua_State *L, int firstline) {
	char buffer[LUA_MAXINPUT] = {0};
	char *b = buffer;
	size_t l;
	const char *prmt = get_prompt(L, firstline);
	int readstatus = lua_readline(L, b, prmt);
	if (readstatus == 0) {
		return 0;  /* no input (prompt will be popped by caller) */
	}
	lua_pop(L, 1);  /* remove prompt */
	l = strlen(b);
	if (l > 0 && b[l-1] == '\n') {  /* line ends with newline? */
		b[--l] = '\0';  /* remove it */
	}
	if (firstline && b[0] == '=') { /* for compatibility with 5.2, ... */
		lua_pushfstring(L, "return %s", b + 1);  /* change '=' to 'return' */
	} else {
		lua_pushlstring(L, b, l);
	}
	return 1;
}

/*
** Try to compile line on the stack as 'return <line>;'; on return, stack
** has either compiled chunk or original line (if compilation failed).
*/
static int addreturn (lua_State *L) {
	const char *line = lua_tostring(L, -1);  /* original line */
	const char *retline = lua_pushfstring(L, "return %s;", line);
	int status = luaL_loadbuffer(L, retline, strlen(retline), "=stdin");
	if (status == LUA_OK) {
		lua_remove(L, -2);  /* remove modified line */
		if (line[0] != '\0') {  /* non empty? */
			lua_saveline(L, line);  /* keep history */
		}
	} else {
		lua_pop(L, 2);  /* pop result from 'luaL_loadbuffer' and modified line */
	}
	return status;
}

/* mark in error messages for incomplete statements */
#define EOFMARK		"<eof>"
#define marklen		(sizeof(EOFMARK)/sizeof(char) - 1)

/*
** Check whether 'status' signals a syntax error and the error
** message at the top of the stack ends with the above mark for
** incomplete statements.
*/
static int incomplete (lua_State *L, int status) {
	if (status == LUA_ERRSYNTAX) {
		size_t lmsg;
		const char *msg = lua_tolstring(L, -1, &lmsg);
		if (lmsg >= marklen && strcmp(msg + lmsg - marklen, EOFMARK) == 0) {
			lua_pop(L, 1);
			return 1;
		}
	}
	return 0;  /* else... */
}

/*
** Read multiple lines until a complete Lua statement
*/
static int multiline (lua_State *L) {
	for (;;) {  /* repeat until gets a complete statement */
		size_t len;
		const char *line = lua_tolstring(L, 1, &len);  /* get what it has */
		int status = luaL_loadbuffer(L, line, len, "=stdin");  /* try it */
		if (!incomplete(L, status) || !pushline(L, 0)) {
			lua_saveline(L, line);  /* keep history */
			return status;  /* cannot or should not try to add continuation line */
		}
		lua_pushliteral(L, "\n");  /* add newline... */
		lua_insert(L, -2);  /* ...between the two lines */
		lua_concat(L, 3);  /* join them */
	}
	printf("debug: multiline\n");
}

/*
** Read a line and try to load (compile) it first as an expression (by
** adding "return " in front of it) and second as a statement. Return
** the final status of load/call with the resulting function (if any)
** in the top of the stack.
*/
static int loadline (lua_State *L) {
	int status;
	lua_settop(L, 0);
	if (!pushline(L, 1)) {
		return -1;  /* no input */
	}
	if ((status = addreturn(L)) != LUA_OK) { /* 'return ...' did not work? */
		status = multiline(L);  /* try as command, maybe with continuation lines */
	}
	lua_remove(L, 1);  /* remove line from the stack */
	lua_assert(lua_gettop(L) == 1);
	return status;
}

/*
** Do the REPL: repeatedly read (load) a line, evaluate (call) it, and
** print any results.
*/
static void doREPL (lua_State *L) {
	int status;
	const char *oldprogname = progname;
	progname = NULL;  /* no 'progname' on errors in interactive mode */
	lua_initreadline(L);
	while ((status = loadline(L)) != -1) {
		if (status == LUA_OK) { 
			status = docall(L, 0, LUA_MULTRET);
		}
		if (status == LUA_OK) {
			l_print(L);
		} else { 
			report(L, status);
		}
	}
	lua_settop(L, 0);  /* clear stack */
	lua_writeline();
	progname = oldprogname;
	
	const char *prmt = get_prompt(L, 1);
	tls_uart_write(UART_NUM, prmt, strlen(prmt));
}

static int pmain (lua_State *L) 
{
	luaL_openlibs(L);  /* open standard libraries */
	luaL_checkversion(L);  /* check that interpreter has correct version */
	print_version();
	lua_gc(L, LUA_GCGEN, 0, 0);  /* GC in generational mode */
	const char *prmt = get_prompt(L, 1);
	printf("\n%s", prmt);
	
	//memcpy(rx_buffer, "print(\"hello\")\n", strlen("print(\"hello\")\n"));
	//doREPL(L);
	
	return 0;
}

static void lua_task(void *sdata)
{
	void *msg;

	uart_init();
	tls_uart_write(UART_NUM, "echo\n", 5);
	
	lua_State* L;
	L = luaL_newstate();
	if (L == NULL) {
		l_message(progname, "cannot create state: not enough memory");
		return;
	}
	printf("Create Lua state success\n");
	lua_pushcfunction(L, &pmain);  /* to call 'pmain' in protected mode */
	int status = lua_pcall(L, 0, 1, 0);  /* do the call */
	if (status != LUA_OK) {}
	int result = lua_toboolean(L, -1);  /* get result */
	
	if (result != 0) {
		printf("Call error result: %d\n", result);
		goto LUA_END;
	}
	
	while (1) {
		tls_os_queue_receive(lua_uart_q, (void **) &msg, 0, 0);
		switch ((u32) msg) {
		case LUA_MSG_UART_RECEIVE_DATA:
		{
			if (isUartDelay) {break;}
			doREPL(L);
			if (strstr(temp_buffer, "wmwifi.ap") != NULL || strstr(temp_buffer, "wmwifi.sta") != NULL) {
				//printf("delay start\n");
				isUartDelay = true;
			}
			/*if (isUartDelay) {
				tls_os_time_delay(1600);
				isUartDelay = false;
				//printf("\ndelay over\n");
				memset(temp_buffer, 0, TEMP_BUFF_SIZE); //clean uart cache
				int readbytes = 0;
				while ((readbytes = tls_uart_read(UART_NUM, (u8 *)temp_buffer, 128)) > 0) {
					//printf("clear2 %d\n", readbytes);
				}
				//printf("\nuart buffer clear\n");
			}*/
		}
			break;
		default:
			break;
		}
	}

LUA_END:
	lua_close(L);
	printf("Error: lua end\n");
}

// PB8 connect to blue LED, light on when the pin output LOW level
static void gpio_init(void)
{
	tls_gpio_cfg(WM_IO_PB_08, WM_GPIO_DIR_OUTPUT, WM_GPIO_ATTR_FLOATING);
	tls_gpio_write(WM_IO_PB_08, 0);	
}

void UserMain(void)
{
	printf("\n----------------------------------------------\n");
	printf("  _                 _   _           _      \n");
	printf(" | |               | \\ | |         | |     \n");
	printf(" | |    _   _  __ _|  \\| | ___   __| | ___ \n");
	printf(" | |   | | | |/ _` | . ` |/ _ \\ / _` |/ _ \\\n");
	printf(" | |___| |_| | (_| | |\\  | (_) | (_| |  __/\n");
	printf(" \\_____/\\__,_|\\__,_\\_| \\_/\\___/ \\__,_|\\___|\n");
	printf("\n----------------------------------------------\n\n");

	tls_os_status_t res = tls_os_queue_create(&lua_uart_q, LUA_QUEUE_SIZE);
	if (res != TLS_OS_SUCCESS) {
		printf("Create queue failed!\n");
	}
	
	tls_os_task_create(NULL, NULL,
                           lua_task,
                           NULL,
                           (void *) lua_task_stk, 
                           LUA_TAST_STK_SIZE * sizeof(u32), 
                           LUA_TASK_PRIO, 0);

#if 0
	CreateDemoTask();
#endif
	//gpio_init();
}

